package com.nidu.demo.log.aop;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.nidu.demo.common.constants.RequestHeaderConstants;
import com.nidu.demo.log.gateway.ApiAccessLogGateway;
import com.nidu.demo.log.model.ApiAccessLog;
import com.nidu.demo.security.core.LoginUser;
import com.nidu.demo.security.util.SecurityUtils;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.MDC;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;

/**
 * API访问日志切面
 *
 * @author auto
 */
@Aspect
@Slf4j
@RequiredArgsConstructor
public class ApiAccessLogAspect {

    private final ApiAccessLogGateway apiAccessLogGateway;

    @Around("@annotation(apiAccessLogAnnotation)")
    public Object around(ProceedingJoinPoint joinPoint, ApiAccessLogging apiAccessLogAnnotation) throws Throwable {
        // 如果禁用日志记录，直接执行
        if (!apiAccessLogAnnotation.enable()) {
            return joinPoint.proceed();
        }

        // 记录开始时间
        LocalDateTime beginTime = LocalDateTime.now();
        
        // 构建日志对象
        ApiAccessLog logEntity = buildApiAccessLog(joinPoint, apiAccessLogAnnotation, beginTime);
        
        Object result = null;
        
        try {
            // 执行业务逻辑
            result = joinPoint.proceed();
            
            // 记录成功结果
            logEntity.setResultCode(200)
                    .setResultMsg("success");
            
            // 记录响应结果
            if (apiAccessLogAnnotation.includeResult() && result != null) {
                try {
                    String responseBody = JSONUtil.toJsonStr(result);
                    // 限制响应体长度，避免过长
                    if (responseBody.length() > 2000) {
                        responseBody = responseBody.substring(0, 2000) + "...";
                    }
                    logEntity.setResponseBody(responseBody);
                } catch (Exception e) {
                    log.warn("序列化响应结果失败", e);
                    logEntity.setResponseBody("序列化失败");
                }
            }
            
            return result;
        } catch (Exception e) {
            // 记录失败结果
            logEntity.setResultCode(500)
                    .setResultMsg(e.getMessage());
            
            throw e;
        } finally {
            // 记录结束时间和执行时长
            LocalDateTime endTime = LocalDateTime.now();
            logEntity.setEndTime(endTime);
            
            long duration = java.time.Duration.between(beginTime, endTime).toMillis();
            logEntity.setDuration((int) duration);
            
            // 异步写入日志
            asyncSaveApiAccessLog(logEntity);
        }
    }

    /**
     * 构建API访问日志对象
     */
    private ApiAccessLog buildApiAccessLog(ProceedingJoinPoint joinPoint,
                                                                   ApiAccessLogging annotation, 
                                                                   LocalDateTime beginTime) {
        ApiAccessLog logEntity = new ApiAccessLog();
        
        // 设置基本信息
        logEntity.setBeginTime(beginTime)
                .setApplicationName("cola-demo")
                .setOperateModule(annotation.operateModule())
                .setOperateName(annotation.operateName())
                .setOperateType(annotation.operateType())
                .setDeleted(false);
        
        // 设置链路追踪ID
        String traceId = MDC.get("traceId");
        if (StrUtil.isNotBlank(traceId)) {
            logEntity.setTraceId(traceId);
        }
        
        // 设置用户信息
        try {
            LoginUser loginUser = SecurityUtils.getLoginUser();
            if (loginUser != null) {
                logEntity.setUserId(loginUser.getId())
                        .setUserType(loginUser.getUserType());
            }
        } catch (Exception e) {
            log.debug("获取登录用户信息失败", e);
        }
        
        // 设置请求信息
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                
                logEntity.setRequestMethod(request.getMethod())
                        .setRequestUrl(request.getRequestURI())
                        .setUserIp(ServletUtil.getClientIP(request))
                        .setUserAgent(request.getHeader(RequestHeaderConstants.USER_AGENT));
                
                // 记录请求参数
                if (annotation.includeArgs()) {
                    try {
                        Object[] args = joinPoint.getArgs();
                        if (args != null && args.length > 0) {
                            String requestParams = JSONUtil.toJsonStr(args);
                            // 限制参数长度，避免过长
                            if (requestParams.length() > 2000) {
                                requestParams = requestParams.substring(0, 2000) + "...";
                            }
                            logEntity.setRequestParams(requestParams);
                        }
                    } catch (Exception e) {
                        log.warn("序列化请求参数失败", e);
                        logEntity.setRequestParams("序列化失败");
                    }
                }
            }
        } catch (Exception e) {
            log.debug("获取请求信息失败", e);
        }
        
        return logEntity;
    }

    /**
     * 异步保存API访问日志
     */
    @Async("apiAccessLogExecutor")
    public void asyncSaveApiAccessLog(ApiAccessLog logEntity) {
        try {
            apiAccessLogGateway.create(logEntity);
            log.debug("API访问日志保存成功，URL: {}, 耗时: {}ms", 
                    logEntity.getRequestUrl(), logEntity.getDuration());
        } catch (Exception e) {
            log.error("API访问日志保存失败", e);
        }
    }
}
